{
  // IsAny

  // 如何判断一个类型是 any 类型呢？要根据它的特性来：

  // any 类型与任何类型的交叉都是 any，也就是 1 & any 结果是 any。

  type IsAny<T> = 'dong' extends 'guang' & T ? true : false;

  type res = IsAny<'dong'>;

  // 之前我们实现 IsEqual 是这样写的：

  type IsEqual<A, B> = (A extends B ? true : false) & (B extends A ? true : false);

  // 问题也出在 any 的判断上：因为 any 可以是任何类型，任何类型也都是 any，所以当这样写判断不出 any 类型来。

  type res2 = IsEqual<'a', any>; // type res2 = true

  // 改进

  type IsEqual2<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2
    ? true
    : false;

  type res3 = IsEqual2<'a', any>; // type res3 = false

  // TS 对这种形式的类型做了特殊处理，是一种 hack 的写法，它的解释要从 TypeScript 源码找答案了，放到原理篇我们一起读下 TypeScript 源码。这里暂时就这样写吧。
}

{
  // IsUnion

  type IsUnion<A, B = A> = A extends B ? ([B] extends [A] ? false : true) : never;

  type res = IsUnion<'1' | '2'>;
  type res2 = IsUnion<['1' | '2']>;
  type res3 = IsUnion<[] | {}>;
}

{
  // IsNever
  // never 在条件类型中也比较特殊，如果条件类型左边是类型参数，并且传入的是 never，那么直接返回 never：

  type TestNever<T> = T extends number ? 1 : 2;

  // type res = never
  type res = TestNever<never>;

  // 所以，要判断 never 类型，就不能直接 T extends number，可以这样写

  type IsNever<T> = [T] extends [never] ? true : false;

  type IsNeverRes = IsNever<never>; // type IsNeverRes = true
}

{
  // 除此以外，any 在条件类型中也比较特殊，如果类型参数为 any，会直接返回 trueType 和 falseType 的合并：

  type TestAny<T> = T extends number ? 1 : 2;

  type res = TestAny<any>; // type res = 1 | 2
}

{
  // IsTuple 元组
  // 元组类型怎么判断呢？它和数组有什么区别呢？
  // 元组类型也是数组类型，但每个元素都是只读的，并且 length 是数字字面量，而数组的 length 是 number。

  // 第一个特性，元组类型也是数组类型，并且每个元素都是只读，这个很好理解。

  // 第二个特性：length

  // type len = 3
  type len = [1, 2, 3]['length'];

  // type len2 = number
  type len2 = number[]['length'];

  // 根据以上两个特性，来判断是否是元组类型的

  type IsTuple<T> = T extends readonly [...params: infer Eles]
    ? NotEqual<Eles['length'], number>
    : false;

  type NotEqual<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2
    ? false
    : true;

  type res = IsTuple<[]>; // true
  type res2 = IsTuple<[number, string]>; // true
  type res3 = IsTuple<number[]>; // false
}

{
  /*
    类型之间是有大小关系的，比如 A 和 B 的交叉类型 A & B 就比联合类型 A | B 小。

    如果从大的类型变为小的类型，那就是类型的缩小，叫做逆变。

    如果从小的类型变为大的类型，那就是类型的放大，叫做协变。

    如果大小没变化，叫做不变。

    我们要实现联合类型转为交叉类型，那就是类型缩小，也就是逆变。

    在 TypeScript 中有函数参数是有逆变的性质的，也就是如果传入联合类型，会返回交叉类型。

    所以联合转交叉可以这样实现 ：
  */

  type UnionToIntersection<U> = (U extends U ? (x: U) => unknown : never) extends (
    x: infer R,
  ) => unknown
    ? R
    : never;

  type res = UnionToIntersection<{ a: string } | { b: number }>;
  /*
    type res = {
      a: string;
    } & {
      b: number;
    }
  */

  // 函数参数的逆变性质一般就联合类型转交叉类型会用，记住就行。
}

{
  // GetOptional 过滤可选的类型 构造一个新类型

  type obj = {
    name: string;
    sex: boolean;
    age?: number;
  };

  type GetOptional<Obj extends Record<string, any>> = {
    [Key in keyof Obj as {} extends Pick<Obj, Key> ? Key : never]: Obj[Key];
  };

  type res = GetOptional<obj>;

  /*
    类型参数 Obj 为待处理的索引类型，类型约束为索引为 string、值为任意类型的索引类型 Record<string, any>。

    用映射类型的语法重新构造索引类型，索引是之前的索引也就是 Key in keyof Obj，但要做一些过滤，也就是 as 之后的部分。

    过滤的方式就是单独取出该索引之后，判断空对象是否是其子类型。

    因为 age 可能为 undefined，也就是索引类型可能是 {}，所以 {} extends Pick<Obj, Key> 就能过滤出可选索引。（可选的意思就是有或者没有，没有的时候就是空的索引类型）

    值的类型依然是之前的，也就是 Obj[Key]。

    这样，就能过滤出所有可选索引，构造成新的索引类型：
  */
}

{
  // GetRequired
  // 实现了 GetOptional，那反过来就是 GetRequired，也就是过滤所有非可选的索引构造成新的索引类型：

  type obj = {
    name: string;
    sex: boolean;
    age?: number;
  };

  type IsRequired<Obj, Key extends keyof Obj> = {} extends Pick<Obj, Key> ? never : Key;

  type res = IsRequired<obj, 'name'>;

  type GetRequired<Obj extends Record<string, any>> = {
    [Key in keyof Obj as IsRequired<Obj, Key>]: Obj[Key];
  };

  // 过滤出了非可选类型
  type res2 = GetRequired<obj>;
}

{
  // RemoveIndexSignature 删除索引类型的可索引签名
  // 索引类型可能有索引，也可能有可索引签名。
  // 比如：

  type Dong = {
    [key: string]: any;
    sleep(): void;
  };

  /*
    这里的 sleep 是具体的索引，[key: string]: any 就是可索引签名，代表可以添加任意个 string 类型的索引。
    如果想删除索引类型中的可索引签名呢？
    同样根据它的性质，其余的索引是字符串字面量类型，而可索引签名的索引是 string 类型。字符串字面量类型可以 infer 提取，string 不可以。
  */

  type RemoveIndexSignature<Obj extends Record<string, any>> = {
    [Key in keyof Obj as Key extends `${infer Str}` ? Str : never]: Obj[Key];
  };

  // type res = { sleep: () => void }
  type res = RemoveIndexSignature<Dong>;

  /*
    类型参数 Obj 是待处理的索引类型，约束为 Record<string, any>。
    通过映射类型语法构造新的索引类型，索引是之前的索引 Key in keyof Obj，但要做一些过滤，也就是 as 之后的部分。
    如果索引是字符串字面量类型，那么就保留，否则返回 never，代表过滤掉。
    值保持不变，也就是 Obj[Key]。
  */
}

{
  // ClassPublicProps
  // 如何过滤出 class 的 public 的属性呢？
  // 也同样是根据它的特性：keyof 只能拿到 class 的 public 索引，private 和 protected 的索引会被忽略。

  class Dong {
    public name: string;
    protected age: number;
    private hobbies: string[];

    constructor() {
      this.name = 'dong';
      this.age = 20;
      this.hobbies = ['sleep', 'eat'];
    }
  }

  // keyof 拿到的只有 name：
  type publicKeys = keyof Dong; // type publicKeys = "name"

  // 所以，我们就可以根据这个特性实现 public 索引的过滤：

  type ClassPublicProps<Obj extends Record<string, any>> = {
    [Key in keyof Obj]: Obj[Key];
  };

  type res = ClassPublicProps<Dong>;
}
